import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;


public class EMAlgo 
{
	// Speichert den Wert (Punkt) und die WKT, dass er zu der Verteilung A gehört. B ergibt sich mit 1-prob.
	public static HashMap<Double, Double> allData = new HashMap<Double, Double>();
	public static double mueA = 1;
	public static double sigmaA = 1;
	
	public static double mueB = 4;
	public static double sigmaB = 1;
	
	// WKT das irgendein Punkt zu der Verteilung A gehört. B ergibt sich so: 1 - prob.
	private double prob = 0.5f; 
	
	private double loglikelihood = 0;
	
	public static void main(String[] argv)
	{
		EMAlgo.emRun();
	}
	
	public static void emRun()
	{
		EMAlgo algo = new EMAlgo();
		algo.readInput();
		
		int counter = 0;
		do 
		{
			algo.loglikelihood = algo.calcLogLikelihood();
			
			// E Step
			algo.calcProb();

			// M Step
			algo.updateParameter();
			
//			for (Double key : algo.allData.keySet()) 
//			{
//				System.out.println();
//			}
			
			System.out.println("Erwartungswert für A: " + mueA + " Standartabweichung für A: " + sigmaA);
			System.out.println("Erwartungswert für B: " + mueB + " Standartabweichung für B: " + sigmaB);
			System.out.println("Like alt:" + algo.loglikelihood + "Like neu: " + algo.calcLogLikelihood());
			System.out.println("###############");
			System.out.println("");
			
		} while (++counter < 26);
	}
	
	// M-Step Berechne Parameter neu
	private void updateParameter()
	{
		// Berechne die WKT neu, dass ein Punkt (kein bestimmter Punkt) zu einer Verteilung gehört.
		// Das ist die Summe über alle WKT's aller Punkte zu der Verteilung A.
//		double sumOfAllProbs = 0;
//		for (Double key : this.allData.keySet()) 
//		{
//			sumOfAllProbs += this.allData.get(key);
//		}
//		this.prob = (1 / this.allData.size()) * sumOfAllProbs;
		
		
		// Berechne mueA und mueB neu.
		double mueUppA = 0, mueUppB = 0;
		for (Double key : allData.keySet()) 
		{
			mueUppA += (allData.get(key) * key);
			mueUppB += ((1-allData.get(key)) * key);
		}
		
		double downA = 0, downB = 0;
		for (Double key : allData.keySet()) 
		{
			downA += allData.get(key);
			downB += (1 - allData.get(key));
		}
		
		mueA = (mueUppA / downA);
		mueB = (mueUppB / downB);
		
		// Calc Varianz
		double varUppA = 0, varUppB = 0;
		for (Double key : allData.keySet()) 
		{
			varUppA += allData.get(key) * (key - mueA) * (key - mueA);
			varUppB +=  (1 - allData.get(key)) * (key - mueB) * (key - mueB);
		}
		
		sigmaA = Math.sqrt((varUppA / downA));
		sigmaB = Math.sqrt((varUppB / downB));
		
	}
	
	private double getGaussianValue(double x, double mue, double sigma)
	{
		double firstCof = 1 / (Math.sqrt(2 * Math.PI) * sigma);
		double expt = -(((x - mue) * (x - mue)) / (2 * sigma * sigma));
		return firstCof * Math.exp(expt);
	}
	
	// Rechnet die WKT aus, dass ein Punkt zu dem Cluster A gehört.
	private void calcProb()
	{
		for (Double key : allData.keySet())  
		{			
			// Errechnet die Wahrscheinlichkeit, dass ein Punkt (j) zu der i-ten Verteilung gehört. 
			// Hier wird nur die WKT zu der ersten Verteilung berechnet. Die WKT für die Verteilung B ergibt sich so: 1 - tau;
			double tau = (prob * this.getGaussianValue(key, mueA, sigmaA)) / (((1 - prob) * this.getGaussianValue(key, mueB, sigmaB)) 
					+ prob * this.getGaussianValue(key, mueA, sigmaA));
			
			allData.put(key, tau);
		}
	}
	
	private double calcLogLikelihood()
	{
		double sum2 = 0;
		for (Double key : allData.keySet()) 
		{
			// Eigentliche Formel: Summe über alle Punkte ln (Summe über alle Verteilungen (prob * Punkt in der Verteilung)) 
			// Da hier aber nur zwei Verteilungen, wird die innere Summe einfach durch eine Addition durchgeführt.
			
			double probThatPointBelongsToA = prob * this.getGaussianValue(key, mueA, sigmaA);
			double probThatPointBelongsToB = (1 - prob) * this.getGaussianValue(key, mueB, sigmaB);
			sum2 += Math.log(probThatPointBelongsToB + probThatPointBelongsToA);
		}
		
		return sum2;
	}
	
	private void readInput() 
	{
		BufferedReader reader;
		try {
			reader = new BufferedReader(new FileReader(System.getProperty("user.dir") + "/data/Session2/EM_data.txt"));
			
			String line;
	        while((line = reader.readLine()) != null ) 
	        {
	        	allData.put(Double.valueOf(line), Math.random());	
	        }
	        reader.close();   
		} 
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}
